package net.minidev.json.parser;

import java.io.IOException;
import java.io.Reader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;

import net.minidev.json.parser.ContainerFactory;
import net.minidev.json.parser.ParseException;
import static net.minidev.json.parser.ParseException.*;
import static net.minidev.json.parser.JSONParser.*;

/**
 * Parser for JSON text. Please note that JSONParser is NOT thread-safe.
 * 
 * @author Uriel Chemouni <uchemouni@gmail.com>
 */
public class JSONParserStream {
	public final static int EOI = -1;
	private int c;
	private ContainerFactory containerFactory;
	private Reader in;
	private int pos;
	private StringBuilder sb = new StringBuilder();
	private String xs;

	//
	//
	//
	//
	//
	//
	//
	//
	/**
	 * use to return Primitive Type, or String, Or JsonObject or JsonArray
	 * generated by a ContainerFactory
	 */
	public Object parse(Reader in) throws ParseException, IOException {
		return parse(in, ContainerFactory.FACTORY);
	}

	/**
	 * use to return Primitive Type, or String, Or JsonObject or JsonArray
	 * generated by a ContainerFactory
	 */
	public Object parse(Reader in, ContainerFactory containerFactory) throws ParseException, IOException {

		this.in = in;
		this.containerFactory = containerFactory;
		this.c = in.read();
		this.pos = 0;
		//
		//
		//
		Object result = readMain(stopX);
		xs = null;
		return result;
	}

	final private void read() throws IOException {
		c = in.read();

		pos++;
	}

	private List<Object> readArray() throws ParseException, IOException {
		List<Object> obj = containerFactory.creatArrayContainer();
		if (c != '[')
			throw new RuntimeException("Internal Error");
		read();

		for (;;) {
			switch (c) {
			case ' ':
			case '\r':
			case '\n':
			case '\t':
				read();
				continue;
			case ']':
				read(); /* unstack */

				return obj;
			case ':':
			case '}':
				throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, (char) c);
			case ',':
				read();
				continue;
			case EOI:
				throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, "EOF");
			default:
				obj.add(readMain(stopArray));
				continue;
			}
		}
	}

	/**
	 * use to return Primitive Type, or String, Or JsonObject or JsonArray
	 * generated by a ContainerFactory
	 */
	private Object readMain(boolean stop[]) throws ParseException, IOException {
		for (;;) {
			switch (c) {
			// skip spaces
			case ' ':
			case '\r':
			case '\n':
			case '\t':
				read();
				continue;
				// invalid stats
			case ':':
			case '}':
			case ']':
				throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c);
				// start object
			case '{':
				return readObject();
				// start Array
			case '[':
				return readArray();
				// start string
			case '"':
			case '\'':
				//
				//
				return readString();
				// string or null
			case 'n':
				xs = readNQString(stop);
				if ("null".equals(xs)) {

					return null;
				}
				//
				//
				//
				return xs;
				// string or false
			case 'f':
				xs = readNQString(stop);
				if ("false".equals(xs)) {

					return Boolean.FALSE;
				}
				//
				//
				//
				return xs;
				// string or true
			case 't':
				xs = readNQString(stop);
				if ("true".equals(xs)) {

					return Boolean.TRUE;
				}
				//
				//
				//
				return xs;
				// string or NaN
			case 'N':
				xs = readNQString(stop);
				//
				//
				if ("NaN".equals(xs)) {

					return Float.valueOf(Float.NaN);
				}
				//
				//
				//
				return xs;
				// digits
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
			case '-':
				//
				//
				return readNumber(stop);
			default:
				//
				//
				//
				//
				return readNQString(stop);
			}
		}
	}

	private String readNQString(boolean[] stop) throws IOException {
		sb.delete(0, sb.length());
		skipNQString(stop);
		return sb.toString().trim();
	}

	private Object readNumber(boolean[] stop) throws ParseException, IOException {
		sb.delete(0, sb.length());
		sb.append((char) c);// skip first char digit or -
		read();
		skipDigits();
		if (c != '.' && c != 'E' && c != 'e') {
			skipSpace();
			if (!stop[c]) {
				// convert string
				skipNQString(stop);
				//
				//
				//
				return sb.toString().trim();
			}
			xs = sb.toString().trim();
			if (xs.length() > 20)
				return new BigInteger(xs);
			try {
				long v = Long.parseLong(xs);
				if (v >= (long) Integer.MIN_VALUE && v <= (long) Integer.MAX_VALUE)
					return Integer.valueOf((int) v);
				return Long.valueOf(v);
			} catch (NumberFormatException e) {
				return new BigInteger(xs);
			}
		}
		if (c == '.') {
			sb.append((char) c);
			read();
			skipDigits();
		}
		if (c != 'E' && c != 'e') {
			skipSpace();
			if (!stop[c]) {
				// convert string
				skipNQString(stop);
				//
				//
				//
				return sb.toString().trim();
			}
			String num = sb.toString().trim();
			if (num.length() > 18) // follow JSjonIJ parssing methode
				return new BigDecimal(num);
			return Double.parseDouble(num);
		}
		sb.append('E');
		read();
		if (c == '+' || c == '-' || c >= '0' && c <= '9') {
			sb.append((char) c);
			read(); // skip first char
			skipDigits();
			skipSpace();
			if (!stop[c]) {
				// convert string
				skipNQString(stop);
				return sb.toString().trim();
			}
			return Double.parseDouble(sb.toString().trim());
		} else {
			skipNQString(stop);
			return sb.toString().trim();
			//
			//
			//
			//
			//
			//
		}
		// throw new ParseException(pos - 1, ERROR_UNEXPECTED_CHAR, null);
	}

	private Map<String, Object> readObject() throws ParseException, IOException {
		Map<String, Object> obj = this.containerFactory.createObjectContainer();
		if (c != '{')
			throw new RuntimeException("Internal Error");

		for (;;) {
			read();
			switch (c) {
			case ' ':
			case '\r':
			case '\t':
			case '\n':
				continue;
			case ':':
			case ']':
			case '[':
			case '{':
				throw new ParseException(pos, ERROR_UNEXPECTED_CHAR, c);
			case '}':
				read(); /* unstack */

				return obj;
			case ',':
				continue;
			case '"':
			case '\'':
			default:
				String key;
				if (c == '\"' || c == '\'')
					key = readString();
				else {
					key = readNQString(stopKey);
				}
				//
				//
				//
				while (c != ':' && c != EOI) {
					read();
				}
				if (c == EOI)
					throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null);
				read(); /* skip : */
				obj.put(key, readMain(stopValue));

				if (c == '}') {
					read(); /* unstack */

					return obj;
				} /* if c==, confinue */
				continue;
			}
		}
	}

	private String readString() throws ParseException, IOException {
		//
		//
		sb.delete(0, sb.length());
		/* assert (c == '\"' || c == '\'') */
		//
		//
		//
		//
		//
		//
		//
		//
		//
		//
		char sep = (char) c;
		for (;;) {
			read();
			switch (c) {
			case EOI:
				throw new ParseException(pos - 1, ERROR_UNEXPECTED_EOF, null);
			case '"':
			case '\'':
				if (sep == c) {
					read();
					return sb.toString();
				}
				sb.append((char) c);
				break;
			case '\\':
				read();
				switch (c) {
				case 't':
					sb.append('\t');
					break;
				case 'n':
					sb.append('\n');
					break;
				case 'r':
					sb.append('\r');
					break;
				case 'f':
					sb.append('\f');
					break;
				case 'b':
					sb.append('\b');
					break;
				case '\\':
					sb.append('\\');
					break;
				case '/':
					sb.append('/');
					break;
				case '\'':
					sb.append('\'');
					break;
				case '"':
					sb.append('"');
					break;
				case 'u':
					sb.append(readUnicode());
					break;
				default:
					break;
				}
				break;
			case '\b':
			case '\t':
			case '\f':
			case '\r':
			case '\n':
				continue;
			default:
				sb.append((char) c);
			}
		}
	}

	private char readUnicode() throws ParseException, IOException {
		int value = 0;
		for (int i = 0; i < 4; i++) {
			value = value * 16;
			read();
			if (c >= '0' && c <= '9')
				value += c - '0';
			else if (c >= 'A' && c <= 'F')
				value += (c - 'A') + 10;
			else if (c >= 'a' && c <= 'f')
				value += (c - 'a') + 10;
			else if (c == EOI)
				throw new ParseException(pos, ERROR_UNEXPECTED_EOF, "EOF");
			else
				throw new ParseException(pos, ERROR_UNEXPECTED_UNICODE, c);
		}
		return (char) value;
	}

	private void skipDigits() throws IOException {
		for (;;) {
			if (c == EOI)
				return;
			if (c < '0' || c > '9')
				return;
			sb.append((char) c);
			read();
		}
	}

	private void skipNQString(boolean[] stop) throws IOException {
		for (;;) {
			if (c == EOI)
				return;
			if (c >= 0 && c <= 125 && stop[c])
				return;
			sb.append((char) c);
			read();
		}
	}

	private void skipSpace() throws IOException {
		for (;;) {
			if (c == EOI)
				return;
			if (c != ' ' && c != '\r' && c != '\t' && c != '\n')
				return;
			sb.append((char) c);
			read();
		}
	}
}
